#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import locale
import os
import shutil
import socket

import psutil
from pexpect import popen_spawn

from common.common import read_result_file
from common.file_common import delete_file
from oracle import logger
from oracle.common.constants import ScriptExitCode, PluginPathConstant, RETURN_INVALID, RestoreType, PexpectResult, \
    OracleRecoverOrder, OracleCompareBackUpVersionResult, OracleStartTypeEnum
from oracle.common.windows_common import write_tmp_file, redirect_backup_path, get_oracle_version, \
    check_oracle_install_type, get_asm_instance, check_sqlplus_status, check_rman_status, execute_windows_cmd, \
    check_path_in_disk_group, execute_windows_rman_cmd, get_db_role
from oracle.services.restore.windows_restore_func import check_dg_exit, create_dirs_from_file, delete_spfile, \
    create_pfile_from_pm, replace_pfile_env_by_newenv, create_db_config_dir, modify_pfile_config_param, \
    build_catalog_datafile_sql, build_catalog_sql, build_newname_datafile_sql, build_rename_log_file, \
    build_rename_datafile_sql, create_spfile_from_pfile, create_init_file, disable_database_bct, oracle_upgrade, \
    set_rac_pwd_file, enable_bct_after_restore, build_rename_log_file_for_standby, clear_redo_log_file, \
    build_rename_temp_file_sql
from oracle.services.restore.windows_sbin_func import unix_timestamp_to_date, get_instance_auth_type, \
    compare_oracle_version, start_open, modify_cluster_property, start_mount, check_database_cdb_type, open_all_pdbs, \
    get_database_status


class WindowsOracleRestore:
    def __init__(self, pid, script_root_path, sub_job_id):
        self.restore_params = {}
        self.ora_version = ''
        self.sql_params = {}
        self.rman_params = {}
        self.db_is_cluster = 0
        self.additional = ''
        self.oracle_is_cdb = ''
        self.auth_type = ''
        self.spfile_location_new = ''
        self.spfile_location = ''
        self.omf_enable = ''
        self.ora_preversion_extend = ''
        self.ora_preversion = ''
        self.database_role = ''
        self.oracle_base = ''
        self.ora_db_group = ''
        self.task_type = 0
        self.resetlogs_id = ''
        self.pfile_name = ''
        self.dbpw_file = ''
        self.upgrade = ''
        self.incar_num = ''
        self.backup_all_version = ''
        self.backup_pre_version = ''
        self.export_oracle_env = ''
        self.rman_enc_section = ''
        self.is_enc_bk = 0
        self.pit = ''
        self.copy_restore = 0
        self.main_backup_path = ''
        self.backup_path_list = ''
        self._pid = sub_job_id
        self._script_root_path = script_root_path
        self._sub_job_id = sub_job_id
        self.db_name = ''
        self.db_instance = ''
        self.db_user = ''
        self.db_user_pwd = ''
        self.oracle_home = ''
        self.asm_user = ''
        self.asm_user_pwd = ''
        self.channels = 0
        self.pit_time = ''
        self.pit_scn = ''
        self.recover_target = ''
        self.recover_path = ''
        self.recover_order = ''
        self.storage_type = ''
        self.asm_sid_name = ''
        self.db_type = ''
        self.enc_algo = ''
        self.enc_key = ''
        self.pfile_pid = ''
        self.restore_by = ''
        self.recover_num = ''
        self.ora_db_user = ''
        self.ora_grid_user = ''
        self.backup = ''
        self.meta_data_path = ''
        self.archive = ''
        self.job_id_list = ''
        self.bct_status = ""
        self.target_db_run_user = ''
        self.target_db_run_user_pwd = ''
        self.restore_files = []
        self.is_start_db = ''

    def get_restore_vars(self, params):
        self.db_name = params['AppName']
        self.db_instance = params['InstanceName']
        self.db_user = params['UserName']
        self.db_user_pwd = params['Password']
        self.oracle_home = params['OracleHome']
        self.oracle_base = params['OracleBase']
        self.asm_user = params['ASMUserName']
        self.asm_user_pwd = params['ASMPassword']
        self.channels = params['Channel']
        self.pit_time = params['pitTime']
        self.pit_scn = params['pitScn']
        self.recover_target = params['recoverTarget']
        self.recover_path = params['recoverPath']
        self.recover_order = params['recoverOrder']
        self.storage_type = params['storType']
        self.asm_sid_name = params['ASMInstanceName']
        self.db_type = params['dbType']
        self.enc_algo = params['EncAlgo']
        self.enc_key = params['EncKey']
        self.pfile_pid = params['pfilePID']
        self.restore_by = params['RestoreBy']
        self.recover_num = params['recoverNum']
        self.ora_db_user = params['OracleInstallUser']
        self.ora_grid_user = params['GridInstallUser']
        self.backup = params['DataPath']
        self.meta_data_path = params['MetaDataPath']
        self.archive = params['LogPath']
        self.job_id_list = params['JobIdList']
        self.bct_status = params['bctStatus']
        self.target_db_run_user = params['targetDbRunUser']
        self.target_db_run_user_pwd = params['targetDbRunUserPwd']
        self.restore_files = params['restoreFiles']
        self.is_start_db = params["isStartDB"]

    def exe_restore(self):
        logger.info(f"Enter function exe_restore, pid:{self._pid}.")
        ret_code = self.define_params()
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Define params failed, pid:{self._pid}.")
            return ret_code
        ret_code = self.check_host_environment()
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Check host environment failed, pid:{self._pid}.")
            return ret_code
        if self.restore_files:
            ret_code = self.file_restore()
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"File restore failed, pid:{self._pid}.")
                return ret_code
            return ScriptExitCode.SUCCESS
        ret_code = self.prepare_restore()
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Prepare restore failed, pid:{self._pid}.")
            return ret_code
        ret_code = self.restore()
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Restore failed, pid:{self._pid}.")
            return ret_code
        logger.info(f"Execute restore success, pid:{self._pid}.")
        return ScriptExitCode.SUCCESS

    def define_params(self):
        logger.info(f"Enter function define_params, pid:{self._pid}.")
        self.backup_path_list = redirect_backup_path(self.backup)
        self.main_backup_path = self.backup_path_list.split(";")[0]
        self.additional = f"{self.main_backup_path}\\additional"
        backup_temp = f"{self.main_backup_path}\\tmp"
        logger.info(f'BACKUPTEMP={backup_temp};ADDITIONAL={self.additional};'
                    f'MainBackupPath={self.main_backup_path}, pid:{self._pid}.')
        encoding = locale.getdefaultlocale()[1]
        scn_dbf_max_content = read_result_file(f"{self.additional}\\scn_dbf_max", encoding=encoding).strip()
        self.define_params_by_scn_dbf(scn_dbf_max_content)
        pit_time_date = ''
        if self.pit_time:
            pit_time_date = unix_timestamp_to_date(self.pit_time)
            logger.info(f"Convert {self.pit_time} to {pit_time_date}, pid:{self._pid}.")
        if self.pit_scn != "0":
            self.pit = f"scn {self.pit_scn}"
        else:
            self.pit = f"time \"to_date('{pit_time_date}', 'YYYY-MM-DD HH24:MI:SS')\""
        if self.channels:
            self.channels = int(self.channels)
        if not self.channels or self.channels == 0:
            self.channels = 4
            logger.warning(F"Setting channels number to {self.channels} by default, pid:{self._pid}.")
        if self.channels > 254:
            logger.error(f"Channels param is invalid, Channels={self.channels}, pid:{self._pid}.")
            return RETURN_INVALID
        self.is_enc_bk = 0
        [self.ora_version, self.ora_preversion, self.ora_preversion_extend] = get_oracle_version(self._pid)
        if self.enc_algo and self.enc_key:
            self.is_enc_bk = 1
            if int(self.ora_preversion) > 11:
                self.rman_enc_section = f"configure encryption algorithm {self.enc_algo};"
            else:
                self.rman_enc_section = f"{self.rman_enc_section} set decryption identified by {self.enc_key};"
        logger.info(f"PID={self._pid};DBINSTANCE={self.db_instance};DBNAME={self.db_name};DBUSER={self.db_user};"
                    f"ASMSIDNAME={self.asm_sid_name};ASMUSER={self.asm_user};DBCHANNEL={self.channels};"
                    f"PIT_TIME={self.pit_time};PIT_SCN={self.pit_scn};BACKUP={self.backup};PIT={self.pit}"
                    f"ARCHIVE={self.archive};RECOVERTARGET={self.recover_target};RECOVERPATH={self.recover_path};"
                    f"RECOVERORDER={self.recover_order};pfilePID={self.pfile_pid};RestoreBy={self.restore_by};"
                    f"RECOVERNUM={self.recover_num};JOBID_LIST={self.job_id_list};ORACLE_HOME={self.oracle_home}")
        return ScriptExitCode.SUCCESS

    def define_params_by_scn_dbf(self, scn_dbf_max_content):
        pit_scn_max = scn_dbf_max_content.split()[0]
        logger.info(f"pit_scn_max={pit_scn_max}")
        if self.pit_scn and int(self.pit_scn) < int(pit_scn_max):
            logger.info(
                f"Base media snapshot used to restore is newer than PIT to restore {self.pit_time}, pid:{self._pid}.")
        if not self.pit_time and not self.pit_scn:
            self.pit_scn = pit_scn_max
        # 0 is arch restore 1 is full restore
        self.copy_restore = RestoreType.ARCHICE_RESTORE
        if not self.archive:
            logger.info(f"ARCHIVE is null, restore by full copy, pid:{self._pid}.")
            self.copy_restore = RestoreType.FULL_RESTORE
        if self.restore_by == 1:
            logger.info(f"Full backup, restore by copy, pid:{self._pid}.")
            self.copy_restore = 1

    def check_host_environment(self):
        logger.info(f"Enter function check_host_environment, pid:{self._pid}.")
        self.db_is_cluster = check_oracle_install_type()
        if self.db_is_cluster > 0:
            # 通过ASM服务获取ASM实例
            self.asm_sid_name = get_asm_instance(self._pid, "OracleASMService")
            if not self.asm_sid_name:
                logger.error(f"Have no asm instance, pid:{self._pid}")
                return RETURN_INVALID
        if not check_sqlplus_status() or not check_rman_status():
            return RETURN_INVALID
        self.auth_type = get_instance_auth_type(self._pid, self.db_instance)
        return ScriptExitCode.SUCCESS

    def check_file_restore_path(self, path):
        # 检查path在windows盘中还是在asm磁盘组中
        if path[0].isalpha():
            if not (os.path.exists(path) and os.path.isdir(path)):
                logger.error(f"Recover path:{self.recover_path} is not exist can not restore, pid:{self._pid}.")
                return False
        else:
            ret = check_path_in_disk_group(self._pid, path, self.asm_sid_name, self.oracle_home)
            if not ret:
                logger.error(f"Recover path:{path} is not exist can not restore, pid:{self._pid}.")
                return False
        return True

    def asm_copy(self, origin, dest):
        os.environ['ORACLE_SID'] = self.asm_sid_name
        os.environ['ORACLE_HOME'] = self.oracle_home
        copy_cmd = f"cmd /c asmcmd cp {origin} {dest}"
        ret, result = execute_windows_cmd(self._pid, copy_cmd.split(), "")
        if not ret:
            logger.info(f"Copy file from {origin} to asm path {dest} failed, pid: {self._pid}.")
            return False
        return True

    def pfile_restore(self, file_info):
        logger.info(f"Start to restore pfile, pid:{self._pid}.")
        pfile_info = file_info[0]
        pfile_name = pfile_info.get("name")
        pfile_path = pfile_info.get("path")
        if pfile_path:
            if not self.check_file_restore_path(pfile_path):
                logger.error(f"Check pfile restore path: {pfile_path} failed, pid={self._pid}.")
                return ScriptExitCode.ERROR_ORACLE_RECOVERPATH_NOT_EXIT
            dest_pfile = os.path.join(pfile_path, f"ebackup-pfile{self.db_instance}.ora")
        else:
            dest_pfile = os.path.join(self.oracle_home, f"dbs\\ebackup-pfile{self.db_instance}.ora")
        if not self.pfile_pid:
            copy_pfile = os.path.join(self.main_backup_path, pfile_name)
        else:
            copy_pfile = f"{PluginPathConstant.WINDOWS_TMP_PATH}/pfile{self.pfile_pid}"
            if not os.path.exists(copy_pfile):
                logger.error(f"User pfile {copy_pfile} is not exit can not restore, pid:{self._pid}.")
                return RETURN_INVALID
            logger.info(f"Restore pfile from user pfile pfile{self.pfile_pid}, pid:{self._pid}")
        try:
            shutil.copy2(copy_pfile, dest_pfile)
        except Exception as exception:
            logger.error(
                f"Copy backup pfile to {dest_pfile} failed, exception={exception}, pid={self._pid}.")
            return ScriptExitCode.ERROR_ORACLE_FILE_RESTORE_COPY_FAILED
        logger.info(f"Restore pfile from user pfile success, pid:{self._pid}.")
        if self.pfile_pid and os.access(copy_pfile, os.R_OK):
            delete_file(copy_pfile)
            logger.info(f"Remove user pfile {copy_pfile} success, pid:{self._pid}.")
        else:
            logger.info(f"Remove user pfile failed, pid:{self._pid}.")
        return ScriptExitCode.SUCCESS

    def copy_restore_files(self, backup_path, dest_path, filetype):
        logger.info(f"Dest {filetype} file path: {dest_path}, source path: {backup_path}.")
        if dest_path[0].isalpha():
            try:
                shutil.copy2(backup_path, dest_path)
            except Exception as exception:
                logger.error(
                    f"Copy backup {filetype} file to {dest_path} failed, exception={exception}, pid={self._pid}.")
                return False
        else:
            if not self.asm_copy(backup_path, dest_path):
                logger.error(f"Copy backup {filetype} file to {dest_path} failed, pid={self._pid}.")
                return False
        return True

    def control_restore(self, file_info):
        logger.info(f"Start to restore control file, pid:{self._pid}.")
        controlfile_info = file_info[0]
        logger.info(f"Control file info: {controlfile_info}.")
        controlfile_name = controlfile_info.get("name")
        controlfile_path = controlfile_info.get("path")
        backup_controlfile = os.path.join(self.main_backup_path, controlfile_name)
        if os.path.exists(os.path.join(self.archive, controlfile_name.replace('/', ''))):
            backup_controlfile = os.path.join(self.archive, controlfile_name.replace('/', ''))
            controlfile_name = os.path.basename(controlfile_name)
        if controlfile_path:
            if not self.check_file_restore_path(controlfile_path):
                logger.error(f"Check controlfile restore path: {controlfile_path} failed, pid={self._pid}.")
                return ScriptExitCode.ERROR_ORACLE_RECOVERPATH_NOT_EXIT
            dest_controlfile = os.path.join(controlfile_path, controlfile_name)
        else:
            controlfile_info_name = os.path.join(self.additional, f"ctrlfiles")
            with open(controlfile_info_name, 'r') as file:
                first_line = file.readline().strip()
            origin_path = os.path.dirname(first_line)
            dest_controlfile = os.path.join(origin_path, controlfile_name)
        if not self.copy_restore_files(backup_controlfile, dest_controlfile, "control"):
            logger.error(f"Restore control file failed, pid={self._pid}.")
            return ScriptExitCode.ERROR_ORACLE_FILE_RESTORE_COPY_FAILED
        return ScriptExitCode.SUCCESS

    def get_datafile_origin_path(self, file_name, file_id):
        with open(file_name, 'r') as file:
            for line in file:
                # 将行按照分号分割
                columns = line.strip().split(';')
                if columns[2] == str(file_id):
                    # 返回路径的信息
                    return os.path.dirname(columns[3])
        logger.error(f"Data file path info not found, pid: {self._pid}.")
        return ""

    def datafile_restore(self, file_info):
        logger.info(f"Start to restore data file, pid:{self._pid}.")
        for data_file_info in file_info:
            logger.info(f"Data file info: {data_file_info}.")
            datafile_name = data_file_info.get("name").replace('/', '')
            backup_datafile = os.path.join(self.main_backup_path, datafile_name)
            datafile_actual_name = os.path.basename(datafile_name)
            datafile_path = data_file_info.get("path")
            if datafile_path:
                if not self.check_file_restore_path(datafile_path):
                    logger.error(f"Check datafile restore path: {datafile_path} failed, pid={self._pid}.")
                    return ScriptExitCode.ERROR_ORACLE_RECOVERPATH_NOT_EXIT
                dest_datafile = os.path.join(datafile_path, datafile_actual_name)
            else:
                datafile_info_name = os.path.join(self.additional, f"dbfiles")
                datafile_id = os.path.basename(os.path.dirname(datafile_name))
                origin_path = self.get_datafile_origin_path(datafile_info_name, datafile_id)
                if not origin_path:
                    logger.error(f"Restore datafile file failed, origin_path is null.")
                    return RETURN_INVALID
                dest_datafile = os.path.join(origin_path, datafile_actual_name)
            if not self.copy_restore_files(backup_datafile, dest_datafile, "data"):
                logger.error(f"Restore data file failed, pid={self._pid}.")
                return ScriptExitCode.ERROR_ORACLE_FILE_RESTORE_COPY_FAILED
        return ScriptExitCode.SUCCESS

    def logfile_restore(self, file_info):
        logger.info(f"Start to restore log file, pid:{self._pid}.")
        for log_file_info in file_info:
            logger.info(f"Log file info: {log_file_info}.")
            logfile_name = log_file_info.get("name").replace('/', '')
            backup_logfile = os.path.join(self.main_backup_path, logfile_name)
            if os.path.exists(os.path.join(self.archive, logfile_name)):
                backup_logfile = os.path.join(self.archive, logfile_name)
            logfile_actual_name = os.path.basename(logfile_name)
            logfile_path = log_file_info.get("path")
            if logfile_path:
                if not self.check_file_restore_path(logfile_path):
                    logger.error(f"Check logfile restore path: {logfile_path} failed, pid={self._pid}.")
                    return ScriptExitCode.ERROR_ORACLE_RECOVERPATH_NOT_EXIT
                dest_logfile = os.path.join(logfile_path, logfile_actual_name)
            else:
                logfile_info_name = os.path.join(self.additional, f"logfiles")
                with open(logfile_info_name, 'r') as file:
                    first_line = file.readline().strip()
                if len(first_line.split(";")) < 2:
                    logger.error(f"Read log file info failed, pid={self._pid}.")
                    return RETURN_INVALID
                origin_path = os.path.dirname(first_line.split(";")[1])
                if not origin_path:
                    logger.error(f"Log file origin path invalid, path: {first_line.split(';')[1]}, pid={self._pid}.")
                    return RETURN_INVALID
                dest_logfile = os.path.join(origin_path, logfile_actual_name)
            if not self.copy_restore_files(backup_logfile, dest_logfile, "log"):
                logger.error(f"Restore data file failed, pid={self._pid}.")
                return ScriptExitCode.ERROR_ORACLE_FILE_RESTORE_COPY_FAILED
        return ScriptExitCode.SUCCESS

    def file_restore(self):
        logger.info(f"Start to exec file restore on Windows, restore files: {self.restore_files}, pid={self._pid}.")
        file_param = dict()
        for files in self.restore_files:
            file_param[files.get('filterTpe')] = files.get('files', '')
        if file_param.get("parameterfile", ""):
            ret_code = self.pfile_restore(file_param.get("parameterfile"))
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error("Restore parameterfile failed!")
                return ret_code
        if file_param.get("controlfile", ""):
            ret_code = self.control_restore(file_param.get("controlfile"))
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error("Restore controlfile failed!")
                return ret_code
        if file_param.get("datafile"):
            ret_code = self.datafile_restore(file_param.get("datafile"))
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error("Restore datafile failed!")
                return ret_code
        if file_param.get("logfile"):
            ret_code = self.logfile_restore(file_param.get("logfile"))
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error("Restore logfile failed!")
                return ret_code
        logger.error("Restore files success")
        return ScriptExitCode.SUCCESS

    def prepare_restore(self):
        logger.info(f"Enter function prepare_restore, pid:{self._pid}.")
        encoding = locale.getdefaultlocale()[1]
        dbinfo_content = read_result_file(f"{self.additional}\\dbinfo", encoding=encoding).strip()
        dbinfo_content_list = dbinfo_content.split(';')
        self.backup_pre_version = dbinfo_content_list[6]
        logger.info(f"Restore version is {self.ora_preversion_extend}, while backup "
                    f"version is {self.backup_pre_version}, pid:{self._pid}.")
        if self.ora_preversion_extend != self.backup_pre_version:
            logger.error(f"restore version not match, pid:{self._pid}.")
            return ScriptExitCode.ERROR_ORACLE_VERSION_DISMATCH

        self.backup_all_version = dbinfo_content_list[9]
        self.upgrade = compare_oracle_version(self._pid, self.ora_version, self.backup_all_version)
        if self.upgrade > 2:
            logger.error("Cannot apply big version to low version")
            return ScriptExitCode.ERROR_ORACLE_VERSION_DISMATCH

        self.incar_num = dbinfo_content_list[7]
        if not self.incar_num:
            logger.error(f"Get backup copy incarnation failed, pid:{self._pid}.")
            return RETURN_INVALID

        # self.recover_target不为2则不检查
        params = {
            "oracle_home": self.oracle_home,
            "asm_sid_name": self.asm_sid_name,
        }
        ret_code = check_dg_exit(self._pid, self.recover_path, self.recover_target, self.additional, params)
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Check dg exit failed, pid:{self._pid}, err_code:{ret_code}")
            return ret_code
        if self.recover_order == 1 and self.db_is_cluster == 1 and self.recover_target == "2":
            # 用srvctl工具删除数据库配置信息
            srvctl_remove_database_cmd = f"cmd /c srvctl remove database -d {self.db_name} -f"
            ret, result = execute_windows_cmd(self._pid, srvctl_remove_database_cmd.split(), "")
            if (not ret) or ("PR" in result and 'PRCD-1120' not in result):
                logger.error(f"Remove database {self.db_name} failed, pid:{self._pid}, err is {result}")
                return RETURN_INVALID

        ret_code = self.check_recover_path()
        if ret_code != ScriptExitCode.SUCCESS:
            return ret_code
        ret_code = self.creating_database_directories(dbinfo_content_list)
        if ret_code != ScriptExitCode.SUCCESS:
            return ret_code
        ret_code = self.config_spfile()
        if ret_code != ScriptExitCode.SUCCESS:
            return ret_code

        self.define_restore_params()
        # restore to other host
        if self.recover_target == "2" or self.recover_path:
            # modify pfile
            self.omf_enable = modify_pfile_config_param(self.restore_params, self.recover_path, self.pfile_name,
                                                        self.db_is_cluster)
        return ScriptExitCode.SUCCESS

    def restore(self):
        logger.info(f"Enter function restore, pid:{self._pid}.")
        if self.recover_target == "2":
            self.create_oracle_sid()
        is_first_task = (self.db_is_cluster != 1 or self.recover_order == 1)
        if is_first_task and create_spfile_from_pfile(self.restore_params,
                                                      self.recover_path,
                                                      self.additional,
                                                      self.pfile_name) != ScriptExitCode.SUCCESS:
            # create spfile from pfile
            logger.error(f"Create spfile from pfile failed, pid:{self._pid}, err_code:{RETURN_INVALID}")
            return RETURN_INVALID
        # create init{DBINSTANCE}.ora
        ret_code = create_init_file(self.restore_params, self.recover_path, self.additional)
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Create init file failed, pid:{self._pid}, err_code:{ret_code}")
            return ret_code

        # only single, restart, the first node of cluster can restore file
        if self.db_is_cluster != 1 or self.recover_order == 1:
            ret_code = self.restore_database()
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Restore database failed, pid:{self._pid}")
                return ret_code
        encoding = locale.getdefaultlocale()[1]
        self.spfile_location = read_result_file(f"{self.oracle_home}\\database\\init{self.db_instance}.ora",
                                                encoding=encoding)
        logger.info(f"is_start_db:{self.is_start_db}")
        is_cluster_and_target_two = self.db_is_cluster == 1 and self.recover_target == "2"
        if is_cluster_and_target_two and self.config_database_with_srvctl() != ScriptExitCode.SUCCESS:
            return RETURN_INVALID
        if self.db_is_cluster == 1:
            self.cluster_set_pwd_file()
        self.oracle_is_cdb = check_database_cdb_type(self._pid, self.db_instance, self.ora_preversion, self.sql_params)
        logger.info(f"Oracle_is_cbd:{self.oracle_is_cdb}, pid:{self._pid}.")
        if self.oracle_is_cdb == 0 and self.is_start_db != str(OracleStartTypeEnum.NOT_START_DB.value):
            ret_code = open_all_pdbs(self._pid, self.db_instance, self.sql_params)
            if ret_code != ScriptExitCode.SUCCESS:
                return ret_code
        instance_status = get_database_status(self._pid, self.db_instance, self.db_user, self.db_user_pwd,
                                              self.is_start_db)
        if not instance_status:
            logger.error(f"Instance status({instance_status}) no open, pid:{self._pid}.")
            return ScriptExitCode.ERROR_INSTANCE_NOSTART
        if os.path.exists(self.pfile_name):
            delete_file(self.pfile_name)
        logger.info(f"Do restore database success, pid:{self._pid}.")
        return ScriptExitCode.SUCCESS

    def cluster_set_pwd_file(self):
        if self.recover_order == 1:
            set_rac_pwd_file(self._pid, self.spfile_location, self.db_name, self.dbpw_file)
        if self.recover_order == 2 and self.is_start_db == str(OracleStartTypeEnum.NOT_START_DB.value):
            instance_status = get_database_status(self._pid, self.db_instance, self.db_user, self.db_user_pwd,
                                                  self.is_start_db)
            if not instance_status:
                logger.info("Database not mounted, begin to start mount.")
                start_mount(self._pid, self.db_instance, self.db_user, self.db_user_pwd)
            else:
                logger.info("Database already running.")
        if self.is_start_db != str(OracleStartTypeEnum.NOT_START_DB.value):
            start_open(self._pid, self.db_instance, self.db_user, self.db_user_pwd)

    def create_oracle_sid(self):
        create_instance_cmd = f"oradim -new -sid {self.db_instance}"
        # 如果运行用户不是administrator，需要使用密码认证
        if self.target_db_run_user and self.target_db_run_user.strip().lower() != 'administrator':
            file_name = f"cmd_{self._pid}.bat"
            file_path = os.path.join(PluginPathConstant.WINDOWS_TMP_PATH, file_name)
            write_tmp_file(file_path, create_instance_cmd)
            try:
                child = popen_spawn.PopenSpawn(file_path, timeout=30)
                child.sendline(self.target_db_run_user_pwd)
                index = child.expect(PexpectResult.CREATE_INSTANCE_RES)
                if index in (0, 1):
                    logger.error(f"create oracle sid {self.db_instance} failed.")
                if psutil.pid_exists(child.pid):
                    process = psutil.Process(child.pid)
                    process.terminate()
            except Exception as err:
                logger.info(f"create instance failed , err: {err}")
            delete_file(file_path)
        else:
            execute_windows_cmd(self._pid, create_instance_cmd.split(), "")

    def define_restore_params(self):
        self.restore_params = {
            'pid': self._pid,
            'db_instance': self.db_instance,
            'db_user': self.db_user,
            'db_user_pwd': self.db_user_pwd,
            'db_name': self.db_name,
            'asm_sid_name': self.asm_sid_name,
            'oracle_home': self.oracle_home,
            'task_type': self.task_type,
            'recover_path': self.recover_path,
            'main_backup_path': self.main_backup_path,
            'backup_path_list': self.backup_path_list,
            'additional': self.additional,
            'archive': self.archive
        }
        self.rman_params = {
            "is_enc_bk": self.is_enc_bk,
            "instance_name": self.db_instance,
            "db_user": self.db_user,
            "db_password": self.db_user_pwd,
            "rman_enc_section": ""
        }
        self.sql_params = {
            "is_silence": 1,
            "db_user": self.db_user,
            "db_password": self.db_user_pwd,
        }

    def restore_controlfile(self):
        logger.info(f"Running RMAN to restore control file, pid:{self._pid}")
        tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/restore_controlfile_{self._pid}.sql"
        tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/restore_controlfile_{self._pid}.rst"
        upper_db_name = self.db_name.upper()
        tmp_sql_content = f"startup nomount pfile='{self.oracle_home}\\database\\init{self.db_instance}.ora';\n" \
                          f"restore controlfile from '{self.main_backup_path}\\controlfile.ctl';\n" \
                          f"startup mount;\n" \
                          f"crosscheck datafilecopy tag 'EBACKUP-{upper_db_name}-DATA';\n" \
                          f"delete noprompt expired datafilecopy all;\n" \
                          f"delete noprompt expired backupset;\n" \
                          f"quit;"
        write_tmp_file(tmp_sql, tmp_sql_content)
        enc_type = 0
        ret_code = execute_windows_rman_cmd(self._pid, self.rman_params, enc_type, tmp_sql, tmp_rst)
        if ret_code != ScriptExitCode.SUCCESS:
            encoding = locale.getdefaultlocale()[1]
            logger.error(
                f"Restore {self.db_name}-{self.db_instance} controlfile failed, ret_code={ret_code}, "
                f"error is {read_result_file(tmp_rst, encoding=encoding)}, pid:{self._pid}")
            delete_file(tmp_sql)
            delete_file(tmp_rst)
            return ret_code
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        logger.info(f"Restore controlfile {self.db_name}-{self.db_instance} succ, pid:{self._pid}.")
        return ScriptExitCode.SUCCESS

    def build_original_restore_database_sql(self, tmp_sql):
        logger.info(f"Enter function build_original_restore_database_sql, pid:{self._pid}.")
        tmp_sql_content = f"RESET DATABASE TO INCARNATION {self.incar_num};\n" \
                          f"RUN\n" \
                          "{\n"
        index = 1
        while index <= self.channels:
            formatted_index = '{:02d}'.format(index)
            tmp_sql_content += f"    allocate channel eRestore{formatted_index} type disk;\n"
            index += 1
        tmp_sql_content += f"    configure device type disk parallelism {self.channels};\n" \
                           f"    catalog start with '{self.main_backup_path}\\log' noprompt;\n"
        # 兼容老日志副本结构
        resetlogs_path = os.path.join(self.archive, f"resetlogs_id{self.resetlogs_id}")
        if os.path.exists(resetlogs_path):
            logger.info(f"Start catalog archive log, {self.archive}\\resetlogs_id{self.resetlogs_id}, pid:{self._pid}.")
            tmp_sql_content += f"    catalog start with '{self.archive}\\resetlogs_id{self.resetlogs_id}' noprompt;\n"
        logger.info(f"Start catalog archive log, log joblist :{self.job_id_list}, pid:{self._pid}.")
        jobid_id_list_arr = self.job_id_list.split(",")
        if not jobid_id_list_arr and self.archive:
            logger.warn(f"jobid_id_list_arr is none")
            tmp_sql_content += f"    catalog start with '{self.archive}' noprompt;"
        else:
            for job_id in jobid_id_list_arr:
                if not job_id:
                    continue
                tmp_sql_content += \
                    f"    catalog start with '{self.archive}\\{job_id}\\resetlogs_id{self.resetlogs_id}' " \
                    f"noprompt;\n"
        tmp_sql_content = build_catalog_datafile_sql(self._pid, self.rman_params, tmp_sql_content,
                                                     self.backup_path_list, self.additional)
        if not tmp_sql_content:
            return RETURN_INVALID
        if self.database_role != "PRIMARY" and self.pit_scn == "0":
            tmp_sql_content += "    RESTORE DATABASE;\n"
            scn_dbf_max_file = os.path.join(self.additional, "scn_dbf_max")
            encoding = locale.getdefaultlocale()[1]
            scn_dbf_max_content = read_result_file(scn_dbf_max_file, encoding=encoding).splitlines()
            scn_dbf_max = scn_dbf_max_content[0].split()[0]
            tmp_sql_content += f"    RECOVER DATABASE until scn {scn_dbf_max};\n"
            tmp_sql_content += f"    RECOVER DATABASE until {self.pit};\n"
        else:
            tmp_sql_content += f"    restore database;\n"
            tmp_sql_content += f"    recover database until {self.pit};\n"
        index = 1
        while index <= int(self.channels):
            formatted_index = '{:02d}'.format(index)
            tmp_sql_content += f"    release channel eRestore{formatted_index};\n"
            index += 1
        tmp_sql_content += "}"
        write_tmp_file(tmp_sql, tmp_sql_content)
        return ScriptExitCode.SUCCESS

    def build_db_restore_rman_script(self, tmp_sql):
        logger.info(f"Enter function build_db_restore_rman_script, pid:{self._pid}.")
        channel_name = ""
        if self.task_type == 0:
            channel_name = "eRestore"
        elif self.task_type == 1:
            encoding = locale.getdefaultlocale()[1]
            scn_dbf_max_content = read_result_file(f"{self.additional}\\scn_dbf_max", encoding=encoding)
            pit_scn_max = scn_dbf_max_content.splitlines()[0].split()[0]
            self.pit = f"scn {pit_scn_max}"
            channel_name = "eLivemount"
        elif self.task_type == 2:
            channel_name = "eInsrestore"
        logger.info(
            f"Begin to exec DB restore taskType {channel_name} to {self.pit}, IS_ENC_BK is {self.is_enc_bk}, "
            f"pid:{self._pid}.")
        tmp_sql_content = f"RESET DATABASE TO INCARNATION {self.incar_num};\n" \
                          f"RUN\n" \
                          "{\n" \
                          "    SET COMMAND ID TO 'ProtectAgent_Restore';\n"
        index = 1
        while index < self.channels:
            formatted_index = '{:02d}'.format(index)
            tmp_sql_content += f"    allocate channel {channel_name}{formatted_index} type disk;\n"
            index += 1

        if self.database_role != "PRIMARY":
            tmp_sql_content += f"sql \"alter system set standby_file_management=''MANUAL''\";\n"

        tmp_sql_content = build_catalog_sql(tmp_sql_content, self.restore_params, self.resetlogs_id, self.job_id_list)
        tmp_sql_content = build_newname_datafile_sql(tmp_sql_content, self.restore_params, self.omf_enable)
        if int(self.ora_preversion) > 11 and self.recover_path:
            if self.database_role != "PRIMARY":
                exec_by_rman = "rman"
                ret_code, tmp_sql_content = build_rename_log_file_for_standby(tmp_sql_content, self.restore_params,
                                                                              self.database_role, exec_by_rman,
                                                                              self.omf_enable)
                if ret_code != ScriptExitCode.SUCCESS:
                    logger.error(f"Build_rename_log_file_for_standby faild, pid:{self._pid}")
                    return RETURN_INVALID
            else:
                ret_code, tmp_sql_content = build_rename_log_file(tmp_sql_content, self.restore_params, self.omf_enable)
                if ret_code != ScriptExitCode.SUCCESS:
                    logger.error(f"Build_rename_log_file faild, pid:{self._pid}")
                    return RETURN_INVALID

        tmp_sql_content = self.build_restore_cmd(channel_name, tmp_sql_content)
        tmp_sql_content += "}"
        write_tmp_file(tmp_sql, tmp_sql_content)
        return ScriptExitCode.SUCCESS

    def build_restore_cmd(self, channel_name, tmp_sql_content):
        # ADG从库任意时间点恢复进入此分支
        # ADG从库直接使用时间点恢复会失败，故先使用数据副本的scn恢复，再使用时间点恢复
        if self.database_role != "PRIMARY" and self.pit_scn == "0":
            tmp_sql_content += f"    RESTORE DATABASE;\n"
            tmp_sql_content = build_rename_datafile_sql(tmp_sql_content, self.restore_params, self.omf_enable,
                                                        self.ora_preversion)
            encoding = locale.getdefaultlocale()[1]
            scn_dbf_max_content = read_result_file(f"{self.additional}\\scn_dbf_max", encoding=encoding)
            pit_scn_max = scn_dbf_max_content.splitlines()[0].split()[0]
            tmp_sql_content += f"    RECOVER DATABASE until scn {pit_scn_max};\n"
            tmp_sql_content += f"    RECOVER DATABASE until {self.pit};\n"
        else:
            tmp_sql_content += f"    SET UNTIL {self.pit};\n"
            tmp_sql_content += f"    RESTORE DATABASE;\n"
            tmp_sql_content = build_rename_datafile_sql(tmp_sql_content, self.restore_params, self.omf_enable,
                                                        self.ora_preversion)
            tmp_sql_content += f"    RECOVER DATABASE;\n"

        # 新位置指定目标位置时，处理临时文件
        if self.recover_target == "2" and self.recover_path:
            tmp_sql_content = build_rename_temp_file_sql(tmp_sql_content, self.restore_params, self.omf_enable,
                                                         self.ora_preversion)
        index = 1
        while index < self.channels:
            formatted_index = '{:02d}'.format(index)
            tmp_sql_content += f"    release channel {channel_name}{formatted_index};\n"
            index += 1
        return tmp_sql_content

    def creating_database_directories(self, dbinfo_content_list):
        logger.info(f"Creating directories for database files, pid:{self._pid}")
        ctrlfiles_path = os.path.join(self.additional, "ctrlfiles")
        create_dirs_from_file(self._pid, ctrlfiles_path, self.asm_sid_name, self.oracle_home)
        dataguardconffiles_path = os.path.join(self.additional, "dataguardconffiles")
        create_dirs_from_file(self._pid, dataguardconffiles_path, self.asm_sid_name, self.oracle_home)
        logfiles_path = os.path.join(self.additional, "logfiles")
        create_dirs_from_file(self._pid, logfiles_path, self.asm_sid_name, self.oracle_home)
        dbfiles_path = os.path.join(self.additional, "dbfiles")
        create_dirs_from_file(self._pid, dbfiles_path, self.asm_sid_name, self.oracle_home)

        self.dbpw_file = os.path.join(self.oracle_home, f"database\\PWD{self.db_instance}.ora")
        self.pfile_name = os.path.join(self.oracle_home, f"dbs\\ebackup-pfile{self.db_instance}.ora")
        self.resetlogs_id = dbinfo_content_list[8]
        self.task_type = 0
        src_dir = os.path.join(self.additional, "dbs")
        dst_dir = self.oracle_home
        try:
            shutil.copytree(src_dir, dst_dir, symlinks=True, dirs_exist_ok=True)
        except Exception as exception:
            logger.error(f"Copy files:{src_dir} fail, exception={exception}, pid={self._pid}.")
            return RETURN_INVALID
        logger.info(f"Copy files:{src_dir} success, pid:{self._pid}")

        logger.info(f"Rename orapw, pid:{self._pid}")
        src_file = os.path.join(self.additional, f"dbs\\PWD{self.db_name}.ora")
        dst_path = f"{self.oracle_home}\\database\\PWD{self.db_instance}.ora"
        delete_file(dst_path)
        try:
            shutil.copy(src_file, dst_path)
        except Exception as exception:
            logger.error(f"Copy file:{src_file} fail, exception={exception}, pid={self._pid}.")
            return RETURN_INVALID
        logger.info(f"copy file:{src_file} success, pid:{self._pid}")
        return ScriptExitCode.SUCCESS

    def check_recover_path(self):
        # 检查recover_path在windows盘中还是在asm磁盘组中
        if self.recover_path:
            if self.recover_path[0].isalpha():
                if not (os.path.exists(self.recover_path) and os.path.isdir(self.recover_path)):
                    logger.error(f"Recover path:{self.recover_path} is not exist can not restore, pid:{self._pid}.")
                    return RETURN_INVALID
            else:
                ret = check_path_in_disk_group(self._pid, self.recover_path, self.asm_sid_name, self.oracle_home)
                if not ret:
                    logger.error(f"Recover path:{self.recover_path} is not exist can not restore, pid:{self._pid}.")
                    return RETURN_INVALID
        return ScriptExitCode.SUCCESS

    def config_database_with_srvctl(self):
        if self.recover_order == 1:
            self.spfile_location_new = self.spfile_location.split("=")[1]
            srvctl_add_database_cmd = f"cmd /c srvctl add database -d {self.db_name} -o {self.oracle_home} -p " \
                                      f"{self.spfile_location_new}"
            ret, result = execute_windows_cmd(self._pid, srvctl_add_database_cmd.split(), "")
            if (not ret) or ("PR" in result):
                logger.error(f"Add database {self.db_name} failed, pid:{self._pid}, err is {result}")
                return RETURN_INVALID
        hostname = socket.gethostname()
        srvctl_add_database_cmd = f"cmd /c srvctl add instance -d {self.db_name} -i {self.db_instance} -n {hostname}"
        execute_windows_cmd(self._pid, srvctl_add_database_cmd.split(), "")
        logger.info(f"Srvctl add instance {self.db_instance} success, pid:{self._pid}")
        return ScriptExitCode.SUCCESS

    def restore_database(self):
        ret_code = self.restore_controlfile()
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Restore controlfile failed, pid:{self._pid}")
            return ret_code
        logger.info(
            f"Begin to exec DB restore taskType {self.task_type} to {self.pit}, ISEncBK is {self.is_enc_bk}, "
            f"pid:{self._pid}.")
        restore_database_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}\\recover_oracle{self._pid}.sql"
        restore_database_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}\\recover_oracle{self._pid}.rst"
        ret_code, self.database_role = get_db_role(self._pid, self.db_user, self.db_user_pwd, self.db_instance)
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Get db role failed, pid:{self._pid}, database_role:{self.database_role}")
            return ret_code
        if self.recover_target == "0":
            ret_code = self.build_original_restore_database_sql(restore_database_sql)
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Execute build_original_restore_database_sql failed, pid:{self._pid}.")
                return ret_code
        else:
            if self.database_role != "PRIMARY" and self.get_log_file_name_convert_dir():
                ret_code = clear_redo_log_file(self._pid, self.db_user, self.db_user_pwd, self.db_instance)
                if ret_code != ScriptExitCode.SUCCESS:
                    logger.error(f"Execute clear_redo_log_file failed, pid:{self._pid}.")
                    return ret_code
            ret_code = self.build_db_restore_rman_script(restore_database_sql)
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Execute build_db_restore_rman_script failed, pid:{self._pid}.")
                return ret_code

        logger.info(f"Begin to restore database, pid:{self._pid}.")
        enc_type = 0
        ret_code = execute_windows_rman_cmd(self._pid, self.rman_params, enc_type, restore_database_sql,
                                            restore_database_rst)
        if ret_code != ScriptExitCode.SUCCESS:
            encoding = locale.getdefaultlocale()[1]
            logger.error(f"Recover database-{self.db_instance} failed, pid:{self._pid}, ret_code:{ret_code}. "
                         f"error is:{read_result_file(restore_database_rst, encoding=encoding)}")
            delete_file(restore_database_sql)
            delete_file(restore_database_rst)
            return ret_code

        logger.info(f"Recover database-{self.db_instance} succ, pid:{self._pid}.")
        delete_file(restore_database_sql)
        delete_file(restore_database_rst)

        ret_code = self.upgrade_database(self.is_start_db)
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Upgrade database {self.db_name} failed, pid:{self._pid}.")
            return ret_code

        return ScriptExitCode.SUCCESS

    def get_log_file_name_convert_dir(self):
        convert_dir = ""
        encoding = locale.getdefaultlocale()[1]
        pfile_name_lines = read_result_file(self.pfile_name, encoding=encoding).splitlines()
        for line in pfile_name_lines:
            if "log_file_name_convert" in line:
                log_file_name_convert_line = line.strip()
                convert_dir = log_file_name_convert_line.split("#")[0].split("=")[1].replace("'", "")
        return convert_dir

    def config_spfile(self):
        # delete spfile which the same instance name
        delete_spfile(self._pid, self.db_instance, self.oracle_home)
        ret_code = create_pfile_from_pm(self._pid, self.pfile_pid, self.main_backup_path, self.db_name, self.pfile_name)
        if ret_code != ScriptExitCode.SUCCESS:
            logger.error(f"Execute create_pfile_from_pm failed, pid:{self._pid}")
            return ret_code

        if self.recover_target == "2":
            ret_code = replace_pfile_env_by_newenv(self._pid, self.additional, self.pfile_name, self.oracle_home,
                                                   self.oracle_base)
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Execute replace_pfile_env_by_newenv failed, pid:{self._pid}")
                return ret_code
            ret_code = create_db_config_dir(self._pid, self.pfile_name, self.db_name, self.asm_sid_name,
                                            self.oracle_home)
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Execute create_db_config_dir failed, pid:{self._pid}")
                return ret_code
        return ScriptExitCode.SUCCESS

    def upgrade_database(self, is_start_db):
        if self.upgrade == 1 and self.db_is_cluster == 1 and self.recover_order == 1:
            modify_cluster_property(self._pid, self.db_instance, False, self.sql_params)
            ret_code = start_mount(self._pid, self.db_instance, self.db_user, self.db_user_pwd)
            if ret_code != ScriptExitCode.SUCCESS:
                logger.error(f"Execute start_mount failed, pid:{self._pid}")
                return ret_code
        disable_database_bct(self._pid, self.db_instance, self.upgrade, self.sql_params, is_start_db)
        enable_bct_after_restore(self._pid, self.db_instance, self.bct_status, self.pfile_name, self.restore_params)
        delete_file(self.pfile_name)
        self.oracle_is_cdb = check_database_cdb_type(self._pid, self.db_instance, self.ora_preversion,
                                                     self.sql_params)
        if self.is_start_db == str(OracleStartTypeEnum.NOT_START_DB.value):
            return ScriptExitCode.SUCCESS
        if self.upgrade == OracleCompareBackUpVersionResult.GT and self.recover_order == OracleRecoverOrder.FIRST:
            oracle_upgrade(self.restore_params, self.oracle_is_cdb, self.ora_preversion)
            if self.db_is_cluster == 1:
                modify_cluster_property(self._pid, self.db_instance, False, self.sql_params)
                start_open(self._pid, self.db_instance, self.db_user, self.db_user_pwd)
        return ScriptExitCode.SUCCESS
